/*
    This file is part of JCM.

    JCM is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    JCM is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with JCM.  If not, see <http://www.gnu.org/licenses/>.
*/

import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
import java.text.DateFormat;
import java.text.SimpleDateFormat;

/**
 *  Métodos padronizados para tratamento de datas.
*/
public class DateUtils
{
  /** Indicador lógico de uso do Java 5. */
  private boolean JAVA5;

  /** Formato padrão de datas nas análises e formatações. */
  private DateFormat dateFormat;

  /**
   * Construtor da classe com uso do valor default do formato de data.
  */
  protected DateUtils()
  {
    JAVA5 = System.getProperty("java.version").startsWith("1.5");
    setDateFormat();
  }

  /**
   * Construtor da classe com uso de valor especificado do formato de data.
   *
   * @param A string contendo o formato das datas.
  */
  protected DateUtils(String dateFormatString)
  {
    JAVA5 = System.getProperty("java.version").startsWith("1.5");
    setDateFormat(dateFormatString);
  }

  /**
   * Assinala o valor default do formato das datas.
  */
  public void setDateFormat()
  {
    dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM, localeBR);
    dateFormat.setLenient(false);
  }

  /**
   * Assinala valor específico do formato das datas.
   *
   * @param dateFormatString A string contendo o formato de data a
   *                         assinalar.
  */
  public void setDateFormat(String dateFormatString)
  {
    dateFormat = new SimpleDateFormat(dateFormatString, localeBR);
    dateFormat.setLenient(false);
  }

  /**
   * Retorna o formato de data em vigência.
   *
   * @return O formato de data em vigência.
  */
  public DateFormat getDateFormat()
  {
    return dateFormat;
  }

  /**
   * Formata uma data de tipo Date usando o formato em vigência.
   *
   * @param d A data de tipo Date a formatar.
   * @return A string contendo a data formatada.
  */
  public String format(Date d)
  {
    return dateFormat.format(d);
  }

  /**
   * Formata uma data em Milliseconds usando o formato em vigência.
   *
   * @param time A data em Milliseconds.
   * @return A string contendo a data formatada.
  */
  public String format(long time)
  {
    return format(new Date(time));
  }

  /**
   * Formata uma data Calendar usando o formato em vigência.
   *
   * @param A data Calendar.
   * @return A string contendo a data formatada.
  */
  public String format(Calendar c)
  {
    return format(c.getTime());
  }

  /**
   * Avalia a string fornecida retornando a data representada como Calendar
   * em modo não leniente no instante ZERO ou null se incompatível com o
   * formato padrão.
   *
   * @param date A string a avaliar, supostamente representando uma data.
   * @return A data Calendar representada pela string.
  */
  public Calendar parse(String date)
  {
    Calendar c = Calendar.getInstance();
    try {
      c.setTime(dateFormat.parse(date));
      c.set(Calendar.HOUR_OF_DAY, 0);
      c.set(Calendar.MINUTE, 0);
      c.set(Calendar.SECOND, 0);
      c.set(Calendar.MILLISECOND, 0);
    }
    catch (java.text.ParseException e) {
      return null;
    }
    catch (IllegalArgumentException e) {
      return null;
    }
    return c;
  }

  /**
   * Calcula o número de dias entre as datas fornecidas.
   *
   * <i>Observação</i>: Usar datas no instante ZERO.
   *
   * @param firstDate A data inicial.
   * @param lastDate A data final.
   * @return o número de dias entre as datas.
  */
  public long daysBetweenDates(Calendar firstDate, Calendar lastDate)
  {
    if (JAVA5)
    {
      return getDifference(firstDate, lastDate) / 86400000;
    }
    return getDifference(firstDate, lastDate, TimeUnit.DAYS);
  }

  /**
   * Retorna o número de meses abrangidos entre as datas fornecidas.
   *
   * <i>Observação</i>: Usar datas no instante ZERO.
   *
   * @param firstDate A data inicial.
   * @param lastDate A data final.
   * @return O número de meses entre as datas.
  */
  static public int getNumIteracoes(Calendar firstDate, Calendar lastDate)
  {
    int iter = 1;
    Calendar c = (Calendar) firstDate.clone();
    c.set(Calendar.DATE, 1);
    c.add(Calendar.MONTH, 1);
    for (long f = lastDate.getTimeInMillis(); c.getTimeInMillis() <= f;)
    {
      c.add(Calendar.MONTH, 1);
      iter++;
    }
    return iter;
  }

  /**
   * Obtêm o número de dias no mês da data fornecida.
   *
   * @param data A data Calendar alvo.
   * @return O número de dias no mês da data.
  */
  static public int daysInMonth(Calendar data)
  {
    return data.getActualMaximum(Calendar.DAY_OF_MONTH);
  }

  /**
   * Obtêm o número de dias no ano da data fornecida.
   *
   * @param data A data Calendar alvo.
   * @return O número de dias no ano da data.
  */
  static public int daysInYear(Calendar data)
  {
    return data.getActualMaximum(Calendar.DAY_OF_YEAR);
  }

  /**
   * Calcula a diferença entre as datas em milisegundos.
   *
   * @param firstDate A data inicial.
   * @param lastDate A data final.
   * @return O número de milisegundos entre as datas.
  */
  static public long getDifference(Calendar firstDate, Calendar lastDate)
  {
    return lastDate.getTimeInMillis() - firstDate.getTimeInMillis();
  }

  /**
   * Calcula a diferença entre as datas na granularidade temporal fornecida.
   *
   * @param firstDate A data inicial.
   * @param lastDate A data final.
   * @return O número de unidades entre as datas na granularidade temporal
   *         fornecida.
  */
  static public long getDifference(Calendar firstDate, Calendar lastDate, TimeUnit units)
  {
    return units.convert(
      getDifference(firstDate, lastDate), TimeUnit.MILLISECONDS);
  }

  /** Apontador para instância única do objeto :: Singleton */
  static private DateUtils instance = null;

  /**
   * Retorna a instância única do objeto.
   *
   * @return A instância única do objeto.
  */
  static public DateUtils getInstance()
  {
    if (instance == null)
    {
      instance = new DateUtils();
    }
    return instance;
  }

  /**
   * Retorna a instância única do objeto com formato de data específico.
   *
   * @param detFormatString A string contendo o formato de data.
   * @return A instância única do objeto.
  */
  static public DateUtils getInstance(String dateFormatString)
  {
    if (instance == null)
    {
      instance = new DateUtils(dateFormatString);
    }
    else {
      instance.setDateFormat(dateFormatString);
    }
    return instance;
  }

  /** Objeto de Localidade exclusiva do Brasil. */
  static public final Locale localeBR = new Locale("pt", "BR");
}
